package com.acme.bankapp.account;

import com.acme.bankapp.domain.bank.Account;

/**
 * Created: Nov 8, 2013 5:32:22 PM
 */

/**
 * @author Andrey Alekov
 * Abstract account
 */
public abstract class AbstractAccount implements Account {
	protected long accountId;
	protected double balance;
	protected AccountType accountType;
	protected AccountState state;

	/**
	 * Create unique accountId based on timestamp.
	 * @return long actual timestamp.
	 */
	protected static long createUniqueAccountId() {
		// TODO: Needs to implement business logic here. Not use time based Id.
		return (long) System.nanoTime();
	}
	
	/**
	 * Constructor.
	 * @param amount the initial value.
	 * @throws IllegalArgumentException when amount is negative
	 * @deprecated Cause it's not persistent by business logic. Use just for study.
	 */
	@Deprecated
	public AbstractAccount(final double amount) throws IllegalArgumentException {
		if (amount < 0d){
			throw new IllegalArgumentException("Balance can't be negative. "
					+ "You try insert next - " + balance);
		}
		this.balance = amount;
		this.state = AccountState.OPENED;
		this.accountId = AbstractAccount.createUniqueAccountId();
	}

	/**
	 * Constructor with account identification number.
	 * @param accountId the unique accountId
	 * @param amount the initial value.
	 * @throws IllegalArgumentException when amount is negative
	 */
	public AbstractAccount(final double amount, final long accountId) {
		if (amount < 0d){
			throw new IllegalArgumentException("Balance can't be negative. "
					+ "You try insert next - " + balance);
		}
		this.balance = amount;
		this.state = AccountState.OPENED;
		this.accountId = accountId;
	}

	/**
	 * Constructor with account identification number and account type
	 * @param accountId the unique accountId
	 * @param amount the initial value.
	 * @throws IllegalArgumentException when amount is negative
	 */
	public AbstractAccount(final double amount, final long accountId, 
			final AccountType accountType) {
		if (amount < 0d){
			throw new IllegalArgumentException("Balance can't be negative. "
					+ "You try insert next - " + balance);
		}
		this.balance = amount;
		this.state = AccountState.OPENED;
		this.accountId = accountId;
		this.accountType = accountType;
	}

	/**
	* Get account unique identifier
	* @return the accountId for this account
	*/
	public long getAccountId() {
		return accountId;
	}

	/**
	* Set new account unique identifier
	* @param accountId the new accountId to set
	*/
	public void setAccountId(long accountId) {
		this.accountId = accountId;
	}

	/**
	 * Set new balance for account.
	 * @param balance the balance to set
	 * @throws IllegalArgumentException if an unexpected argument passed
	 */
	public void setBalance(final double balance) throws IllegalArgumentException {
		if (balance < 0d){
			throw new IllegalArgumentException("Balance can't be negative. "
					+ "You try insert next - " + balance);
		}
		this.balance = balance;
	}

	/**
	 * Return actual balance.
	 * @return double actual balance value.
	 */
	public double getBalance() {
		return balance;
	}

	/**
	 * Get account state.
	 * @return the state
	 */
	public AccountState getState() {
		return state;
	}

	/**
	 * Set account state.
	 * @param state the state to set
	 */
	public void setState(AccountState state) {
		this.state = state;
	}

	/**
	 * Get actual account type
	 * @return the accountType
	 */
	public AccountType getAccountType() {
		return accountType;
	}

	/**
	 * Set new AccountType
	 * @param accountType the accountType to set
	 */
	public abstract void setAccountType(AccountType accountType) throws UnsupportedOperationException;

	
	/*
	 * (non-Javadoc)
	 * @see com.acme.bankapp.domain.bank.Account#getDecimalValue()
	 */
	@Override
	public long getDecimalValue() {
		return Math.round(this.balance);
	}
	
	/**
	 * Return maximum amount to withdraw.
	 * @return double value of maximum amount to withdraw
	 */
	public abstract double maximumAmountToWithdraw();

	/**
	 * Increase account with amount.
	 * @param amount value to deposit actual balance.
	 */
	public abstract void deposit(final double amount) throws AccountClosedException;

	/**
	 * Decrease account with amount.
	 * @param amount to withdraw.
	 * @throws OverDraftLimitExceededException when overdraft exceeds
	 * @throws NotEnoughFundsException when not enough balance to withdraw
	 */
	public abstract void withdraw(final double amount) 
			throws OverDraftLimitExceededException, NotEnoughFundsException, AccountClosedException;
	
	/**
	 * Check account for OPEN status.
	 * @return boolean true if account is open
	 */
	public boolean isOpened() {
		return state == AccountState.OPENED;
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "Actual balance = " + this.balance;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (int) (accountId ^ (accountId >>> 32));
		result = prime * result
				+ ((accountType == null) ? 0 : accountType.hashCode());
		result = prime * result + ((state == null) ? 0 : state.hashCode());
		return result;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof AbstractAccount)) {
			return false;
		}
		AbstractAccount other = (AbstractAccount) obj;
		if (accountId != other.accountId) {
			return false;
		}
		if (accountType != other.accountType) {
			return false;
		}
		if (state != other.state) {
			return false;
		}
		return true;
	}	
}
